#include "eltooltip.h"
#include <QPaintEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPropertyAnimation>
#include <QTimer>
#include "../basic/eltheme.h"

const qreal ElTooltip::Padding_Lr = 10;
const qreal ElTooltip::Padding_Tb = 5;
const qreal ElTooltip::Spacing = 5;
const qreal ElTooltip::Triangle_H = 6;

ElTooltip::ElTooltip(ElComponent *target, const QString &content)
{
    setTarget(target);
    setContent(content);
    initAnimation();
    initTimer();
    initPalette();
    hide();
}

ElComponent *ElTooltip::target() const
{
    return _target;
}

void ElTooltip::setTarget(ElComponent *target)
{
    if (_target == target)
        return;
    _target = target;
    updatePos();
}

QPoint ElTooltip::getRelativePosition() const
{
    QPoint pos = _target->pos();
    QWidget *ptr = _target;
    while (ptr->parent() != nullptr && ptr->parent() != parent()) {
        ptr = dynamic_cast<QWidget *>(ptr->parent());
        pos += ptr->pos();
    }
    return pos;
}

ElTooltip::Effect ElTooltip::effect() const
{
    return _effect;
}

void ElTooltip::setEffect(Effect effect)
{
    if (_effect == effect)
        return;
    _effect = effect;
    initPalette();
    update();
}

QString ElTooltip::content() const
{
    return _content;
}

void ElTooltip::setContent(const QString &content)
{
    if (_content == content)
        return;
    _content = content;
    update();
}

ElTooltip::Placement ElTooltip::placement() const
{
    return _placement;
}

void ElTooltip::setPlacement(Placement placement)
{
    if (_placement == placement)
        return;
    _placement = placement;
    update();
}

qreal ElTooltip::offset() const
{
    return _offset;
}

void ElTooltip::setOffset(qreal offset)
{
    if (_offset == offset)
        return;
    _offset = offset;
    update();
}

bool ElTooltip::isArrowVisible() const
{
    return _arrowVisible;
}

void ElTooltip::setArrowVisible(bool b)
{
    if (_arrowVisible == b)
        return;
    _arrowVisible = b;
    update();
}

int ElTooltip::openDelay() const
{
    return _openDelay;
}

void ElTooltip::setOpenDelay(int openDelay)
{
    if (_openDelay == openDelay)
        return;
    _openDelay = openDelay;
    update();
}

bool ElTooltip::isManual() const
{
    return _manual;
}

void ElTooltip::setManual(bool b)
{
    if (_manual == b)
        return;
    _manual = b;
    update();
}

bool ElTooltip::isEnterable() const
{
    return _enterable;
}

void ElTooltip::setEnterable(bool b)
{
    if (_enterable == b)
        return;
    _enterable = b;
    update();
}

int ElTooltip::hideAfter() const
{
    return _hideAfter;
}

void ElTooltip::setHideAfter(int hideAfter)
{
    if (_hideAfter == hideAfter)
        return;
    _hideAfter = hideAfter;
    update();
}

void ElTooltip::fade()
{
    // 200 milliseconds as enterable's threshold
    QTimer::singleShot(200, Qt::CoarseTimer, this, [=] {
        if (_enterable && isEntered)
            return;
        animation->setDirection(QAbstractAnimation::Backward);
        animation->start();
        if (timer->isActive())
            timer->stop();
        timer->start();
    });
}

bool ElTooltip::event(QEvent *e)
{
    if (e->type() == QEvent::EnabledChange) {
        update();
    }
    return ElComponent::event(e);
}

void ElTooltip::paintEvent(QPaintEvent *e)
{
    if (_target == nullptr) {
        qDebug() << "[ElTooltip]ElTooltip should set a target component.";
        e->ignore();
        return;
    }
    if (!isEnabled()) {
        e->ignore();
        return;
    }
    QPainter painter(this);

    // draw text first to get bounding rect because we need to fit the text.
    QRectF br;
    painter.drawText(QRectF(), Qt::AlignCenter, _content, &br);

    // height of triangle counts
    auto w = br.width() + 2 * Padding_Lr + (_arrowVisible && (_placement >= 6) ? Triangle_H : 0);
    auto h = br.height() + 2 * Padding_Tb + (_arrowVisible && (_placement < 6) ? Triangle_H : 0);
    resize(w, h);

    // then draw rect and arrow by replacement
    painter.setBrush(_palette.color(ElPalette::Background));
    QPen pen(_palette.color(ElPalette::Border));
    painter.setPen(pen);
    QRectF drawingRect = getDrawRect();
    QPointF p1, p2, p3;
    switch (_placement) {
    case Top:
    case TopStart:
    case TopEnd:
        p1 = QPointF(drawingRect.center().x(), drawingRect.bottom());
        p2 = QPointF(drawingRect.center().x() - Triangle_H, drawingRect.bottom() - Triangle_H);
        p3 = QPointF(drawingRect.center().x() + Triangle_H, drawingRect.bottom() - Triangle_H);
        drawingRect.setBottom(drawingRect.bottom() - Triangle_H);
        break;
    case Bottom:
    case BottomStart:
    case BottomEnd:
        p1 = QPointF(drawingRect.center().x(), drawingRect.top());
        p2 = QPointF(drawingRect.center().x() - Triangle_H,drawingRect.top() + Triangle_H);
        p3 = QPointF(drawingRect.center().x() + Triangle_H,drawingRect.top() + Triangle_H);
        drawingRect.setTop(drawingRect.top() + Triangle_H);
        break;
    case Left:
    case LeftStart:
    case LeftEnd:
        p1 = QPointF(drawingRect.right(), drawingRect.center().y());
        p2 = QPointF(drawingRect.right() - Triangle_H, drawingRect.center().y() - Triangle_H);
        p3 = QPointF(drawingRect.right() - Triangle_H, drawingRect.center().y() + Triangle_H);
        drawingRect.setRight(drawingRect.right() - Triangle_H);
        break;
    case Right:
    case RightStart:
    case RightEnd:
        p1 = QPointF(drawingRect.left(), drawingRect.center().y());
        p2 = QPointF(drawingRect.left() + Triangle_H, drawingRect.center().y() - Triangle_H);
        p3 = QPointF(drawingRect.left() + Triangle_H, drawingRect.center().y() + Triangle_H);
        drawingRect.setLeft(drawingRect.left() + Triangle_H);
        break;
    default:
        break;
    }

    qreal radius = height() / 8.0;
    QRectF rect = drawingRect;

    QPainterPath roundedRectPath;
    roundedRectPath.addRoundedRect(rect, radius, radius);

    QPainterPath trianglePath;
    trianglePath.addPolygon(QPolygonF({p1, p2, p3}));
    auto drawPath = roundedRectPath.united(trianglePath);

    painter.drawPolygon(drawPath.simplified().toFillPolygon());

    painter.setPen(_palette.color(ElPalette::Foreground));
    painter.drawText(drawingRect, Qt::AlignCenter, _content);

    movePos();

    e->accept();
}

void ElTooltip::showEvent(QShowEvent *e)
{
    updatePos();
    raise();
    animation->setDirection(QAbstractAnimation::Forward);
    auto f = [=] {
        animation->start();
        if (_hideAfter > 0) {
            if (timer->isActive())
                timer->stop();
            timer->start();
        }
    };
    if (_openDelay > 0)
        QTimer::singleShot(_openDelay, Qt::CoarseTimer, this, f);
    else
        f();
    e->accept();
}

void ElTooltip::enterEvent(QEnterEvent *e)
{
    isEntered = true;
    e->accept();
}

void ElTooltip::leaveEvent(QEvent *e)
{
    isEntered = false;
    fade();
    e->accept();
}

void ElTooltip::initPalette()
{
    QColor bgColor, fgColor, bdColor;
    bgColor = _effect ? Qt::white : ThemeColor::textPrimaryColor;
    fgColor = _effect ? ThemeColor::textPrimaryColor : Qt::white;
    bdColor = ThemeColor::textPrimaryColor;
    _palette.setColor(ElPalette::Background, bgColor);
    _palette.setColor(ElPalette::Foreground, fgColor);
    _palette.setColor(ElPalette::Border, bdColor);
}

void ElTooltip::initAnimation()
{
    animation = new QPropertyAnimation(this, "opacity", this);
    animation->setDuration(300);
    animation->setEasingCurve(QEasingCurve::OutQuad);
    animation->setStartValue(0);
    animation->setEndValue(1);
}

void ElTooltip::initTimer()
{
    timer = new QTimer(this);
    timer->setSingleShot(true);
    timer->setInterval(animation->duration());
    connect(timer, &QTimer::timeout, this, [=] {
        if (animation->direction() == QAbstractAnimation::Forward)
            QTimer::singleShot(_hideAfter, Qt::CoarseTimer, this, &ElTooltip::fade);
        else
            hide();
    });
}

void ElTooltip::movePos()
{
    QRect r = rect();
    auto geo = _target->geometry();
    geo.moveTo(getRelativePosition());
    switch (_placement) {
    case Top:
        r.moveCenter(QPoint(geo.center().x() - _offset, geo.y() - height() / 2 - Spacing));
        move(r.topLeft());
        break;
    case TopStart:
        move(QPoint(geo.x() - _offset, geo.y() - height() - Spacing));
        break;
    case TopEnd:
        move(QPoint(geo.right() - width() - _offset, geo.y() - height() - Spacing));
        break;
    case Bottom:
        r.moveCenter(QPoint(geo.center().x() + _offset, geo.bottom() + height() / 2 + Spacing));
        move(r.topLeft());
        break;
    case BottomStart:
        move(QPoint(geo.x() + _offset, geo.bottom() + Spacing));
        break;
    case BottomEnd:
        move(QPoint(geo.right() - width() + _offset, geo.bottom() + Spacing));
        break;
    case Left:
        r.moveCenter(QPoint(geo.x() - width() / 2 - Spacing, geo.center().y() - _offset));
        move(r.topLeft());
        break;
    case LeftStart:
        move(QPoint(geo.x() - width() - Spacing, geo.y() - _offset));
        break;
    case LeftEnd:
        move(QPoint(geo.x() - width() - Spacing, geo.bottom() - height() - _offset));
        break;
    case Right:
        r.moveCenter(QPoint(geo.right() + width() / 2 + Spacing, geo.center().y() + _offset));
        move(r.topLeft());
        break;
    case RightStart:
        move(QPoint(geo.right() + Spacing, geo.y() + _offset));
        break;
    case RightEnd:
        move(QPoint(geo.right() + Spacing, geo.bottom() - height() + _offset));
        break;
    default:
        break;
    }
}

void ElTooltip::updatePos()
{
    QPoint pos = _target->pos();
    QWidget *ptr = _target;
    while (ptr->parent() != nullptr) {
        ptr = dynamic_cast<QWidget *>(ptr->parent());
        if (ptr->parent() == nullptr) {
            break;
        }
        pos += ptr->pos();
    }
    setParent(ptr);
    move(pos.x(), pos.y() - Spacing - height());
}
